package top.rainbowecho.article.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.rainbowecho.article.mapper.*;
import top.rainbowecho.article.service.IArticleService;
import top.rainbowecho.article.util.RedisProxy;
import top.rainbowecho.article.vo.*;
import top.rainbowecho.common.exception.BlogException;
import top.rainbowecho.common.util.CacheKey;
import top.rainbowecho.model.field.BrowseField;
import top.rainbowecho.model.field.LikeField;
import top.rainbowecho.model.field.ReplyField;
import top.rainbowecho.model.percona.*;
import top.rainbowecho.model.vo.ReplyTreeVo;

import javax.annotation.Resource;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

import static cn.hutool.core.collection.CollUtil.isEmpty;

/**
 * <p>
 * 存储文章数据 服务实现类
 * </p>
 *
 * @author 杨健
 * @since 2020-07-25
 */
@Service
@Slf4j
@Transactional(rollbackFor = BlogException.class)
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements IArticleService {
    private ArticleMapper articleMapper;

    private ArticleTagMapper articleTagMapper;

    private ArticleInfoMapper articleInfoMapper;

    private LikeMapper likeMapper;

    private ReplyMapper replyMapper;

    private BrowseMapper browseMapper;

    private RedisProxy redisProxy;

    @Autowired
    public void setRedisProxy(RedisProxy redisProxy) {
        this.redisProxy = redisProxy;
    }

    @Resource
    public void setBrowseMapper(BrowseMapper browseMapper) {
        this.browseMapper = browseMapper;
    }

    @Resource
    public void setReplyMapper(ReplyMapper replyMapper) {
        this.replyMapper = replyMapper;
    }

    @Resource
    public void setLikeMapper(LikeMapper likeMapper) {
        this.likeMapper = likeMapper;
    }

    @Resource
    public void setArticleInfoMapper(ArticleInfoMapper articleInfoMapper) {
        this.articleInfoMapper = articleInfoMapper;
    }

    @Resource
    public void setArticleTagMapper(ArticleTagMapper articleTagMapper) {
        this.articleTagMapper = articleTagMapper;
    }

    @Resource
    public void setArticleMapper(ArticleMapper articleMapper) {
        this.articleMapper = articleMapper;
    }

    /**
     * 根据上传的md格式的内容，进行解析并存储到数据库
     *
     * @param uploadVo 上传对象
     * @return 保存到数据库的数据
     */
    @Override
    public Article saveUploadArticle(ArticleUploadVo uploadVo) throws IOException {
        ArticleConvertVo articleConvert = articleConvert(uploadVo.getArticle().getInputStream());
        ArticleVo articleVo = new ArticleVo(articleConvert, uploadVo);

        LocalDateTime now = LocalDateTime.now();
        // 插入article记录
        Article article = new Article().setUserId(articleVo.getAuthor()).setCover(articleVo.getCover())
                .setText(articleVo.getText()).setTitle(articleVo.getTitle()).setSummary(articleVo.getSummary())
                .setCreateDate(now).setUpdateTime(now).setCategoryId(articleVo.getCategory());
        this.articleMapper.insert(article);

        // 批量写入articleTag关联表
        for (String tagId : articleVo.getTags()) {
            ArticleTag articleTag = new ArticleTag().setTagId(tagId).setArticleId(article.getId());
            this.articleTagMapper.insert(articleTag);
        }

        return article;
    }

    /**
     * 分页查询article信息
     *
     * @param articleQueryVo 查询条件包装对象
     * @param page 分页参数
     * @return 信息列表
     */
    @Override
    public List<ArticleInfoVo> findArticles(ArticleQueryVo articleQueryVo, Pageable page) {
        List<ArticleInfo> infos = this.articleMapper.selectArticleInfoList(articleQueryVo, page);
        // 将info视图查询结果包装成视图类返回
        return infos.stream().map(e -> {
            ArticleInfoVo infoVo = new ArticleInfoVo();
            BeanUtil.copyProperties(e, infoVo);
            return infoVo;
        }).collect(Collectors.toList());
    }

    /**
     * 用户点击一篇文章，进行阅读
     *
     * @param articleId 文章id
     * @param userId 用户名
     * @return 文章信息
     */
    @Override
    public ArticleInfoVo readArticle(String articleId, String userId) {

        // 查询当前用户的信息与文章视图信息
        ArticleInfo articleInfo = this.articleInfoMapper.selectByArticleId(articleId);

        // 更新文章浏览记录
        Wrapper<Browse> browseQuery = new QueryWrapper<Browse>().eq(BrowseField.ARTICLE_ID, articleId)
                .eq(BrowseField.USER_ID, userId);
        Browse oldBrowse = this.browseMapper.selectOne(browseQuery);
        if (oldBrowse == null) {
            Browse latestBrowse = new Browse(articleId, userId, LocalDateTime.now());
            this.browseMapper.insert(latestBrowse);
        } else {
            oldBrowse.setLatestTime(LocalDateTime.now());
            this.browseMapper.update(oldBrowse, browseQuery);
        }

        // 查看用户是否点赞
        QueryWrapper<Like> likeQueryWrapper = new QueryWrapper<Like>().eq(LikeField.USER_ID, userId).eq(LikeField.ARTICLE_ID, articleId);
        Like like = this.likeMapper.selectOne(likeQueryWrapper);
        boolean hasLike = like != null;
        ArticleInfoVo infoVo = new ArticleInfoVo();
        BeanUtil.copyProperties(articleInfo, infoVo);
        infoVo.setIsLike(hasLike);

        // TODO 需要对reply的树形构造进行测试
        QueryWrapper<Reply> replyQueryWrapper = new QueryWrapper<Reply>().eq(ReplyField.ARTICLE_ID, articleId);
        List<Reply> replies = this.replyMapper.selectList(replyQueryWrapper);
        List<ReplyTreeVo> treeReplies =  parseReplyTree(replies);
        infoVo.setReplies(treeReplies);
        return infoVo;
    }

    @Override
    public List<Article> findTopThree() {
        return this.redisProxy.getFromCache(CacheKey.TOPS, this.articleMapper::selectTopThree);
    }

    @Override
    public void updateArticleTop(Article topArticle) {
        this.articleMapper.updateById(topArticle);
        this.redisProxy.evict(CacheKey.TOPS);
    }

    @Override
    public List<ArticleInfo> findTypeTopThree(int type) {
        // 最热，三者按权重进行计算
        if (type == 1) {
            List<ArticleInfo> allArticle = this.articleInfoMapper.selectList(null);
            return this.redisProxy.getFromCache(CacheKey.TOP_HOT, () -> allArticle.stream()
                    .sorted(Comparator.comparing(ArticleInfo::calculateWeight).reversed())
                    .limit(3).collect(Collectors.toList()));
        }

        // 最受欢迎，点赞数最多
        if (type == 2) {
            return this.redisProxy.getFromCache(CacheKey.TOP_LIKE, this.articleInfoMapper::selectTopThreeLike);
        }

        // 最多评论，评论数最多
        if (type == 3) {
            return this.redisProxy.getFromCache(CacheKey.TOP_REPLY, this.articleMapper::selectTopThreeReply);
        }

        // 最多阅读，阅读数最多
        if (type == 4) {
            return this.redisProxy.getFromCache(CacheKey.TOP_BROWSE, this.articleInfoMapper::selectTopThreeBrowse);
        }

        return ImmutableList.of();
    }

    /**
     * 将列表形式的reply转换为树形结构数据
     *
     * @param replies reply列表
     * @return 输入是空的话，直接返回一个空树形数据列表；反之则返回树形结构数据
     */
    private static List<ReplyTreeVo> parseReplyTree(List<Reply> replies) {
        if (isEmpty(replies)) {
            return ImmutableList.of();
        }
        // 获取所有父节点
        List<ReplyTreeVo> parentVos = replies.stream().filter(e -> e.getParentId() == null).map(e -> {
            ReplyTreeVo replyTreeVo = new ReplyTreeVo();
            BeanUtil.copyProperties(e, replyTreeVo);
            return replyTreeVo;
        }).collect(Collectors.toList());

        for (ReplyTreeVo e : parentVos) {
            recursiveTree(e, replies);
        }
        return parentVos;
    }

    /**
     * 递归获取某一个parent的所有子评论
     *
     * @param parent 父节点
     * @param replies 所有回复
     * @return 树形数据
     */
    private static ReplyTreeVo recursiveTree(ReplyTreeVo parent, List<Reply> replies) {
        for (Reply reply : replies) {
            if (StringUtils.equals(reply.getParentId(), parent.getId())) {
                ReplyTreeVo replyTreeVo = new ReplyTreeVo();
                BeanUtil.copyProperties(reply, replyTreeVo);
                recursiveTree(replyTreeVo, replies);
                parent.getChildren().add(replyTreeVo);
            }
        }
        return parent;
    }

    /**
     * 从md格式的文件流中提取标题，概述和具体内容信息，并转为html格式
     *
     * @param is 文件流
     * @return 转换成功的文章转换对象
     */
    private ArticleConvertVo articleConvert(InputStream is) {
        List<String> contents = new ArrayList<>();
        IoUtil.readUtf8Lines(is, contents);

        // 识别出标题
        String title = contents.stream().filter(e -> StringUtils.contains(e, "#")).findFirst().orElse("");
        title = title.replace("#", "");
        title = title.trim();

        log.debug("上传文章文件识别出的标题：" + title);

        // 识别出摘要
        String summary = contents.stream()
                .filter(e -> StringUtils.isNotBlank(e) && StringUtils.containsNone(e, "#"))
                .findFirst().orElse("");
        log.debug("上传文章文件识别出的概述：" + summary);

        StringBuilder sb = new StringBuilder();
        contents.forEach(sb::append);
        log.debug("上传文章文件的具体内容: " + sb.toString());

        return new ArticleConvertVo(title, summary, sb.toString());
    }

}
